/*
 * Copyright (c) 2019-2022. Alexey Antipin and others.
 * https://github.com/Evreke/warships
 *
 * Licensed under the Apache License 2.0
 */

import kotlinx.serialization.Serializable

@Serializable
data class Coordinates(
  val x: Int,
  val y: Int,
)

@Serializable
data class Board(
  val field: MutableList<MutableList<Int>>,
  val size: Int = 10,
  val isMasked: Boolean = false
) {

  fun getCell(coordinates: Coordinates) = field[coordinates.y][coordinates.x]

  fun placeShip(coordinates: Coordinates, ship: Ship) {
    // Checks before place
    when {
      isOutOfBounds(coordinates, ship) -> {
        throw RuntimeException("Ship couldn't be placed outside of field")
      }

      hasProhibitedCellsOnPath(coordinates, ship) -> {
        throw RuntimeException("Ship placement rules violated")
      }
    }

    placeShipInternal(coordinates, ship)
  }

  private fun isOutOfBounds(coordinates: Coordinates, ship: Ship) = when (ship.direction) {
    Direction.HORIZONTAL -> coordinates.x < 0 || coordinates.x + ship.size > field.size
    Direction.VERTICAL -> coordinates.y < 0 || coordinates.y + ship.size > field.size
  }

  private fun hasProhibitedCellsOnPath(coordinates: Coordinates, ship: Ship): Boolean {
    for (i in 0 until ship.size) {
      val (x, y) = when (ship.direction) {
        Direction.HORIZONTAL -> coordinates.x + i to coordinates.y
        Direction.VERTICAL -> coordinates.x to coordinates.y + i
      }

      if (hasShipsInSector(x, y, ship)) {
        println("Cannot place ship at x=$x, y=$y. Prohibited position")
        return true
      }
    }

    return false
  }

  private fun hasShipsInSector(x: Int, y: Int, shipBeingPlaced: Ship): Boolean {
    val minDx = if (x > 0) -1 else 0
    val maxDx = if (x < field.size - 1) 1 else 0
    val minDy = if (y > 0) -1 else 0
    val maxDy = if (y < field.size - 1) 1 else 0

    for (dx in minDx..maxDx) {
      for (dy in minDy..maxDy) {
        val adjacentCell = field[y + dy][x + dx]
        // Ignore if adjacent cells have shipBeingPlaced
        if (adjacentCell != FieldConstants.EMPTY && adjacentCell != shipBeingPlaced.hashCode()) {
          return true
        }
      }
    }
    return false
  }


  private fun placeShipInternal(coordinates: Coordinates, ship: Ship) {
    for (i in 0 until ship.size) {
      val (x, y) = when (ship.direction) {
        Direction.HORIZONTAL -> coordinates.x + i to coordinates.y
        Direction.VERTICAL -> coordinates.x to coordinates.y + i
      }

      field[y][x] = ship.hashCode()
    }
  }

  companion object {
    private const val boardSize = 10
    fun initBoard(): Board {
      val emptyField = mutableListOf<MutableList<Int>>().also { list ->
        repeat(boardSize) {
          list.add(MutableList(boardSize) { FieldConstants.EMPTY })
        }
      }

      return Board(emptyField, boardSize)
    }
  }
}

fun Board.placeShipsForDemo() {
  placeShip(Coordinates(0, 0), SmallShip(Direction.HORIZONTAL))
  placeShip(Coordinates(0, 2), SmallShip(Direction.HORIZONTAL))
  placeShip(Coordinates(0, 4), SmallShip(Direction.VERTICAL))
  placeShip(Coordinates(0, 6), SmallShip(Direction.HORIZONTAL))
  placeShip(Coordinates(3, 0), MediumShip(Direction.VERTICAL))
  placeShip(Coordinates(5, 0), MediumShip(Direction.HORIZONTAL))
  placeShip(Coordinates(3, 6), BigShip(Direction.VERTICAL))
  placeShip(Coordinates(5, 8), BigShip(Direction.HORIZONTAL))
  placeShip(Coordinates(9, 0), HugeShip(Direction.VERTICAL))
}

fun Board.maskBoard(): Board {
  val maskedField = mutableListOf<MutableList<Int>>()
  this.field.forEach { ints ->
    val maskedRow = MutableList(this.size) { FieldConstants.EMPTY }
    ints.forEachIndexed { dx, cell ->
      if (cell == FieldConstants.WRECKED || cell == FieldConstants.SHOT) {
        maskedRow[dx] = cell
      }
    }
    maskedField.add(maskedRow)
  }
  return Board(maskedField, isMasked = true)
}